/*
 * Copyright 2008 Andrej Herich
 * 
 * http://azetclient.yw.sk/
 * http://azetclient.googlecode.com/
 * 
 * This file is part of AzetClient.
 * 
 * AzetClient is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * AzetClient is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with AzetClient.  If not, see <http://www.gnu.org/licenses/>.
 */

package sk.yw.azetclient.managers;

import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import org.apache.log4j.Logger;
import sk.yw.azetclient.Main;
import sk.yw.azetclient.azet.AzetConnection;
import sk.yw.azetclient.connection.ConnectionException;
import sk.yw.azetclient.connection.SiteConnection;
import sk.yw.azetclient.model.Buddy;
import sk.yw.azetclient.model.Message;
import sk.yw.azetclient.model.MessageBean;
import sk.yw.azetclient.preferences.AbstractHandler;
import sk.yw.azetclient.preferences.Handler;

/**
 *
 * @author error216
 */
public class ConnectionManager {
    
    private static final Logger logger =
            Logger.getLogger(ConnectionManager.class);
    
    private SiteConnection connection = new AzetConnection();
    
    private BuddyListManager buddyListManager;
    private MessageThreadManager messageThreadManager;
    
    private static final String BUDDY_LIST_REFRESH_INTERVAL =
            "azetclient.buddyList.interval";
    private long buddyListRefreshInterval;
    private static final String MESSAGE_RETRIEVAL_INTERVAL =
            "azetclient.message.interval";
    private long messageRetrievalInterval;
    
    private List<ConnectionManagerListener> listeners =
            new ArrayList<ConnectionManagerListener>();
    private List<ErrorListener> errorListeners =
            new ArrayList<ErrorListener>();
    
    private Timer timer;
    
    private class RefreshBuddyListTask extends TimerTask {
        @Override
        public void run() {
            refreshBuddyList();
        }
    };
    
    private class RetrieveMessagesTask extends TimerTask {
        @Override
        public void run() {
            retrieveMessages();
        }
    };
    
    private Handler propertiesHandler = new AbstractHandler() {
        
        private boolean buddyListRefreshIntervalChanged = false;
        private boolean messageRetrievalIntervalChanged = false;
        
        public void preferenceChanged(String name, String oldValue,
                String newValue) {
            if (BUDDY_LIST_REFRESH_INTERVAL.equals(name)) {
                buddyListRefreshInterval = Long.parseLong(newValue);
                buddyListRefreshIntervalChanged = true;
            }
            if (MESSAGE_RETRIEVAL_INTERVAL.equals(name)) {
                messageRetrievalInterval = Long.parseLong(newValue);
                messageRetrievalIntervalChanged = true;
            }
        }
        
        @Override
        public void endApply() {
            if (buddyListRefreshIntervalChanged
                    || messageRetrievalIntervalChanged) {
                resheduleAll();
                buddyListRefreshIntervalChanged = false;
                messageRetrievalIntervalChanged = false;
            }
        }
    };
    
    private static ConnectionManagerListener defaultListener =
            new ConnectionManagerListener() {

        public void authenticationSucceeded(ConnectionManagerEvent event) {
            logger.info("Authentication succeeded.");
        }

        public void authenticationFailed(ConnectionManagerEvent event) {
            logger.warn("Authentication failed.");
        }

        public void messageNotSent(ConnectionManagerEvent event) {
            try {
                logger.warn("Unable to send message: " +
                        ((Message) event.getSubject()).getId());
            } catch (Throwable throwable) {
                logger.error("Unable to send message and error while retrieving message id.", throwable);
            }
        }
    };
    
    private static ErrorListener defaultErrorListener = new ErrorListener() {

        public void errorOccured(ErrorEvent event) {
            try {
                logger.error("", event.getThrowable());
            } catch (Throwable throwable) {
                logger.error("Error while retrieving error.", throwable);
            }
        }
    };
    
    public ConnectionManager(BuddyListManager buddyListManager,
            MessageThreadManager messageThreadManager) {
        if (buddyListManager == null)
            throw new IllegalArgumentException("Null pointer in buddyListManager");
        if (messageThreadManager == null)
            throw new IllegalArgumentException("Null pointer in messageThreadManager");
        
        this.buddyListManager = buddyListManager;
        this.messageThreadManager = messageThreadManager;
        
        try {
            this.buddyListRefreshInterval = Integer.valueOf(
                    Main.getProperty(BUDDY_LIST_REFRESH_INTERVAL));
        } catch (NumberFormatException ex) {
            logger.warn("Unable to parse property " + BUDDY_LIST_REFRESH_INTERVAL
                    + "; defaulting to 30000");
            this.buddyListRefreshInterval = 30000;
        }
        try {
            this.messageRetrievalInterval = Integer.valueOf(
                    Main.getProperty(MESSAGE_RETRIEVAL_INTERVAL));
        } catch (NumberFormatException ex) {
            logger.warn("Unable to parse property " + MESSAGE_RETRIEVAL_INTERVAL
                    + "; defaulting to 30000");
            this.messageRetrievalInterval = 30000;
        }
        
        addConnectionManagerListener(defaultListener);
        addErrorListener(defaultErrorListener);
        
        Main.addPreference("azetclient.buddyList.interval", 
                String.valueOf(buddyListRefreshInterval), propertiesHandler);
        Main.addPreference("azetclient.message.interval",
                String.valueOf(messageRetrievalInterval), propertiesHandler);
    }
    
    public synchronized void resheduleAll() {
        logger.info("Resheduling tasks.");
        cancelAll();
        timer = new Timer();
        
        timer.schedule(
                new RefreshBuddyListTask(),
                getBuddyListRefreshInterval(),
                getBuddyListRefreshInterval());
        
        timer.schedule(
                new RetrieveMessagesTask(),
                getMessageRetrievalInterval(),
                getMessageRetrievalInterval());
    }
    
    public synchronized void cancelAll() {
        if (timer != null) timer.cancel();
    }
    
    public synchronized void addConnectionManagerListener(
            ConnectionManagerListener listener) {
        listeners.add(listener);
    }
    
    public synchronized void removeConnectionManagerListener(
            ConnectionManagerListener listener) {
        if (!listeners.remove(listener)) {
            logger.warn("Unable to remove MessageThreadManagerListener.");
        }
    }
    
    public synchronized void addErrorListener(ErrorListener listener) {
        errorListeners.add(listener);
    }
    
    public synchronized void removeErrorListener(ErrorListener listener) {
        if (!errorListeners.remove(listener)) {
            logger.warn("Unable to remove ErrorListener.");
        }
    }
    
    protected void fireErrorOccured(Throwable throwable) {
        List<ErrorListener> listenersList =
                new ArrayList<ErrorListener>(errorListeners);
        ErrorEvent event = new ErrorEvent(this, throwable);
        for (ErrorListener listener : listenersList) {
            listener.errorOccured(event);
        }
    }
    
    protected void fireAuthenticationSucceeded(Buddy user) {
        List<ConnectionManagerListener> listenersList =
                new ArrayList<ConnectionManagerListener>(listeners);
        ConnectionManagerEvent event = new ConnectionManagerEvent(this,
                ConnectionManagerEvent.Type.AUTHENTICATION_SUCCEEDED, user);
        
        for (ConnectionManagerListener listener : listenersList) {
            listener.authenticationSucceeded(event);
        }
    }
    
    protected void fireAuthenticationFailed(Buddy user) {
        List<ConnectionManagerListener> listenersList =
                new ArrayList<ConnectionManagerListener>(listeners);
        ConnectionManagerEvent event = new ConnectionManagerEvent(this,
                ConnectionManagerEvent.Type.AUTHENTICATION_FAILED, user);
        
        for (ConnectionManagerListener listener : listenersList) {
            listener.authenticationFailed(event);
        }
    }
    
    protected void fireMessageNotSent(Message message) {
        List<ConnectionManagerListener> listenersList =
                new ArrayList<ConnectionManagerListener>(listeners);
        ConnectionManagerEvent event = new ConnectionManagerEvent(this, 
                ConnectionManagerEvent.Type.MESSAGE_NOT_SENT, message);
        
        for (ConnectionManagerListener listener : listenersList) {
            listener.messageNotSent(event);
        }
    }
    
    public synchronized void authenticate(Buddy user, String password) {
        try {
            boolean authenticated = connection.authenticate(user, password);
            
            if (authenticated) {
                refreshBuddyList();
                retrieveMessages();
                resheduleAll();
                fireAuthenticationSucceeded(user);
            } else {
                fireAuthenticationFailed(user);
            }
        } catch (ConnectionException ex) {
            fireErrorOccured(ex);
        }
    }
    
    public synchronized boolean isAuthenticated() {
        try {
            return connection.isAuthenticated();
        } catch (ConnectionException ex) {
            fireErrorOccured(ex);
            return false;
        }
    }
    
    public synchronized void refreshBuddyList() {
        if (!isAuthenticated())
                throw new IllegalStateException("Not authenticated");
        
        logger.info("Buddy list refreshing started.");
        try {
            List<Buddy> buddies = connection.getBuddies();
            
            buddyListManager.setBuddies(buddies);
        } catch (ConnectionException ex) {
            fireErrorOccured(ex);
        } finally {
            logger.info("Buddy list refreshing ended.");
        }
    }
    
    public synchronized void retrieveMessages() {
        if (!isAuthenticated())
                throw new IllegalStateException("Not authenticated");
        
        logger.info("Message retrieval started.");
        try {
            List<MessageBean> messages = connection.getMessages();
            
            for (MessageBean message : messages) {
                if (buddyListManager.getBuddy(message.getSender().getName())
                        != null) {
                    buddyListManager.addBuddy(message.getSender());
                } else {
                    buddyListManager.addOther(message.getSender());
                }
                
                messageThreadManager.addMessage(message);
            }
        } catch (ConnectionException ex) {
            fireErrorOccured(ex);
        } finally {
            logger.info("Message retrieval ended.");
        }
    }
    
    public synchronized void sendMessage(Message message) {
        if (!isAuthenticated())
                throw new IllegalStateException("Not authenticated");
        
        try {
            if (!connection.sendMessage(message)) {
                fireMessageNotSent(message);
            }   
        } catch (ConnectionException ex) {
            fireErrorOccured(ex);
        }
    }
    
    public synchronized void disconnect() {
        if (!isAuthenticated())
                throw new IllegalStateException("Not authenticated");
        
        try {
            connection.disconnect();
        } catch (ConnectionException ex) {
            fireErrorOccured(ex);
        }
    }

    public synchronized long getBuddyListRefreshInterval() {
        return buddyListRefreshInterval;
    }

    public synchronized void setBuddyListRefreshInterval(
            long buddyListRefreshInterval) {
        this.buddyListRefreshInterval = buddyListRefreshInterval;
    }

    public synchronized long getMessageRetrievalInterval() {
        return messageRetrievalInterval;
    }

    public synchronized void setMessageRetrievalInterval(
            long messageRetrievalInterval) {
        this.messageRetrievalInterval = messageRetrievalInterval;
    }

}
